#ifndef GTL_SERIALIZATION_HEADER_GUARD
#define GTL_SERIALIZATION_HEADER_GUARD

#include "gtlCompilerSpecific.h"
#include <map>

class Serializer;

template <typename T>
void SerializeClass(void* object, Serializer& serializer, const char* description);

class Serializer
{
public:
  template<typename T>
  class IsClass {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(test<T>(0)) == 1 };
    enum { No = !Yes };
  };

  template<typename T>
  struct IsPointer { static const bool Yes = false; static const bool No = true; };
  template<typename T>
  struct IsPointer<T*> { static const bool Yes = true; static const bool No = false; };

  template <typename T>
  void SerializePointer(T& object, const char* description)
  {

  }

  template <typename T>
  void SerializePointer(T* object, const char* description)
  {
    printf("Serializing pointer (%s)\n", description);
    void* pointer = (void*)object;
    if (mType == GatheringPointerIDInfo)
    {
      PointerID& ptr = mPointers[pointer];
      ptr.Pointer = pointer;
    } else if (mType == Serializing)
    {
      PointerID& ptr = mPointers[pointer];
      if (ptr.WasSerialized)
      {
        WriteInt('dbpt');
        WritePtr(pointer);
      } else
      {
        Serialize(*object);
      }
    }
  }

  template <typename T>
  void SerializeSimple(T& object, const char* description)
  {
    if (mType == GatheringPointerIDInfo)
    {
      MarkSerialized(&object);
    } else if (mType == Serializing)
    {
      WritePtr(&object);
      WriteBytes((const char*)&object, sizeof(T));
    }
    //printf("Serializing simple (size = %i) (%s)\n", sizeof(object), description);
  }

  

  template <typename T>
  void SerializeDynamicArray(T*& object, size_t elements, const char* description)
  {
    printf("Dynamic Array: %s\n", description);

    if (mType == Deserializing)
    {
      elements = (size_t)ReadInt();
      delete[] object;
      object = new T[elements]; /// TODO: provide support for custom allocators
    }

    for (size_t i=0; i<elements; i++)
    {
      Serialize(object[i], description);
    }
  }

  template <typename T>
  void SerializeStaticArray(T* object, size_t elements, const char* description)
  {
    printf("Static Array: %s\n", description);

    if (mType == Deserializing)
    {
      elements = (size_t)ReadInt();
    }

    for (size_t i=0; i<elements; i++)
    {
      Serialize(object[i], description);
    }
  }

  void SerializeObjectStart(void* object, const char* className, uint32_t classNameHash, const char* description)
  {
    if (mType == GatheringPointerIDInfo)
      MarkSerialized(object);
    else if (mType == Serializing)
    {
      //WriteInt((uint32_t)object);
    }

    printf("Class Type: %s (%i) (comment: %s)\n", className, classNameHash, description);
  }

  void SerializeComment(const char* comment)
  {
    if (mType == Deserializing)
    {
      uint32_t size = ReadInt();
      Skip(size);
    } else if (mType == Serializing)
    {
      //uint32_t size = strlen(comment);
      //WriteInt(size);
      //WriteBytes(comment, size);
    }
  }

  void SerializeLiteral(uint32_t literal)
  {
    if (mType == Deserializing)
    {
      uint32_t lit = ReadInt();
      if (lit != literal)
      {
        mFailed = true;
        return;
      }
    }

    if (mType == Serializing)
    {
      if (WriteInt(literal))
      {
        mFailed = true;
        return;
      }
    }
  }

  /// NEVER EVER EVER serialize a function pointer. Just fucking don't. It will never work.
  template <typename T>
  void Serialize(T& object, const char* description = 0)
  {
    if (IsClass<T>::Yes)
    {
      SerializeClass<T>((void*)&object, *this, description);
    } else if (IsPointer<T>::Yes)
    {
      SerializePointer(object, description);
    } else
    {
      SerializeSimple(object, description);
    }
  }

  template <typename T, size_t SIZE>
  void Serialize(T (&object)[SIZE], const char* description = 0)
  {
    SerializeStaticArray(&object[0], sizeof(object)/sizeof(object[0]), description);
  }

  bool IsSerializing()
  {
    return mType >= Serializing && mType < Reflecting;
  }
  bool IsDeserializing()
  {
    return mType >= Deserializing && mType < Serializing;
  }

  template <typename T>
  void StartSerialization(T& object, FILE* file, const char* description = 0)
  {
    mFile = file;
    mType = GatheringPointerIDInfo;
    Serialize(object); Serialize(object);
    mType = Serializing;
    Serialize(object);
  }
protected:
  FILE* mFile;
  int ReadInt()
  {
    return 0;
  }
  int WriteInt(uint32_t lit)
  {
    //printf("Writing int %i\n", lit);
    fwrite(&lit, sizeof(uint32_t), 1, mFile);
    //return 0;
    return 0;
  }
	int WritePtr(void* lit)
	{
		//printf("Writing int %i\n", lit);
		fwrite(&lit, sizeof(void*), 1, mFile);
		//return 0;
		return 0;
	}
  void Skip(uint32_t size)
  {
    fseek(mFile, size, SEEK_CUR);
    //printf("Skipping %i bytes\n", size);
  }
  void WriteBytes(const char* bytes, size_t size)
  {
    fwrite(bytes, 1, size, mFile);
  }
  struct PointerID
  {
    void* Pointer;
    bool WasSerialized;

    PointerID() : Pointer(0), WasSerialized(false)
    {

    }
  };
  void MarkSerialized(void* ptr)
  {
    if (mPointers.find(ptr) != mPointers.end())
    {
      mPointers[ptr].WasSerialized = true;
    }
  }
  std::map<void*, PointerID> mPointers;
  enum
  {
    Deserializing,
      RecreatingPointerDB,
    Serializing,
      GatheringPointerIDInfo,
    Reflecting,
      BuildingReflectionDatabase,
  };
  int mType;
  bool mFailed;
};

#define GTL_ENABLE_CLASS_SERIALIZATION(type) \
  template <> \
  void SerializeClass< type >(void* object, Serializer& serializer, const char* description) { \
    type * obj = (type *)object; \
    serializer.SerializeObjectStart(object, #type, GTL::Strings::Hash(#type, strlen(#type)), description); \
    obj->Serialize(serializer); \
  }

#endif